package study.lagou.com.sqlSession;

import study.lagou.com.config.BoundSql;
import study.lagou.com.pojo.Configuration;
import study.lagou.com.pojo.MappedStatement;
import study.lagou.com.utils.GenericTokenParser;
import study.lagou.com.utils.ParameterMapping;
import study.lagou.com.utils.ParameterMappingTokenHandler;
import study.lagou.com.utils.SelefJavaClass;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 功能描述
 * @Author houjh
 * @Email: happyxiaohou@gmail.com
 * @Date: 2021-2-22 22:10
 */
public class SimpleExecutor implements Executor {

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //5、执行SQL
        ResultSet resultSet = getPreparedStatement(configuration,mappedStatement,params).executeQuery();
        //6、封装返回结果集
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        ArrayList<Object> objectArrayList = new ArrayList<>();
        while (resultSet.next()){
            Object resultObj = resultTypeClass.newInstance();
            //获取到resultSet的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //获取元数据中的字段名，注意获取字段名的时候下标需要从1开始，所以我们这里的for循环遍历下标是从1开始的
                String columnName = metaData.getColumnName(i);
                //获取字段值
                Object value = resultSet.getObject(columnName);
                //使用反射或内省，根据数据库表和实体的对应关系，完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                //获取到PropertyDescriptor对象的写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                //通过写方法将值写入到对象中
                writeMethod.invoke(resultObj,value);
            }
            objectArrayList.add(resultObj);
        }
        return (List<E>) objectArrayList;
    }

    @Override
    public Integer update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //5、执行SQL
        int i = getPreparedStatement(configuration,mappedStatement,params).executeUpdate();
        return i;
    }

    /**
     * 将获取PreparedStatement方法和设置参数方法提取到公共方法中
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    private PreparedStatement getPreparedStatement(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception {
        //1、注册驱动、获取连接
        Connection connection = configuration.getDataSource().getConnection();
        //2、获取SQL信息，并对SQL语句进行转换，在转换过程中需要对#{}里面的值进行解析和存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        //3、获取预处理对象preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //4、设置参数信息
        String paramterType = mappedStatement.getParamterType();
        //4.1、通过参数全路径获取到参数对象
        Class<?> paramterTypeClass = getClassType(paramterType);
        //4.2、通过反射设置参数信息,先获取到标记解析器解析出来的#{}中的属性信息，然后循环遍历进行参数设置
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        //这里需要判断一下参数类型，如果参数类型是java常用的数据类型，那么直接传入参数，否则才通过反射处理设置参数信息
        if(SelefJavaClass.isJavaClass(paramterTypeClass)){
            for(int i= 0;i<params.length;i++){
                preparedStatement.setObject(i+1,params[i]);
            }
        } else {
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                //通过反射根据属性名称获取到具体实体类对应的属性
                Field declaredField = paramterTypeClass.getDeclaredField(parameterMapping.getContent());
                //设置对属性值进行暴力访问
                declaredField.setAccessible(true);
                //设置从哪个对象中获取对应的属性值
                Object o = declaredField.get(params[0]);
                //设置preparedStatement对应的参数信息，注意下标从1开始
                preparedStatement.setObject(i+1,o);
            }
        }
        return  preparedStatement;
    }

    /**
     * 通过类路径获取到对应的实体类信息
     * @param paramterType
     * @return
     * @throws ClassNotFoundException
     */
    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if(paramterType != null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    /**
     * 完成对#{}的解析工作：1、将#{}使用?进行代替 2、解析出#{}里面的值进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类：配合标记处理器完成对占位符的处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        //标记处理器中的三个参数分别是开始标记、结束标记和标记处理类信息
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //通过标记处理器的parse方法解析出来的SQL
        String parseSql = tokenParser.parse(sql);
        //由标记处理类从#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappingList = parameterMappingTokenHandler.getParameterMappings();
        //将解析出来的SQL和#{}中的参数封装到BoundSql对象中
        BoundSql boundSql = new BoundSql(parseSql, parameterMappingList);
        return boundSql;
    }

}
